import { convertMatrix } from '@amcax/renderer';
import { useEffect } from 'react';
import { Matrix4, Quaternion, Vector3 } from 'three-legacy';
import {
  PickedProduct,
  generateProductPickObj,
} from '../../../../common/components/pick-input/pick-product';
import { ctx2 } from '../../../../module';
import { refDocNodes } from '../../../../pages/product-support/assembly-funcs';
import { AssemblyPreview } from '../../types/preview-types';

export class AssemblyPreviewAnimator {
  private intervalId: NodeJS.Timeout | null = null;
  private pickedNode: PickedProduct | null = null;

  constructor(
    private executionResult: AssemblyPreview,
    private duration = 500,
  ) {
    this.animatePreview();
  }

  private animatePreview() {
    const frameCount = 60 * (this.duration / 1000);
    const { dxid, matrix } = this.executionResult;
    const node = refDocNodes.get(dxid);
    const lastMatrix = node.matrix4Array.at(-1);
    const newMatrix = convertMatrix(matrix);

    const { assisObj } = generateProductPickObj(node, null, true);
    const startPosition = new Vector3();
    const startQuaternion = new Quaternion();
    const startScale = new Vector3();
    lastMatrix.decompose(startPosition, startQuaternion, startScale);

    const endPosition = new Vector3();
    const endQuaternion = new Quaternion();
    const endScale = new Vector3();
    newMatrix.decompose(endPosition, endQuaternion, endScale);

    const matrices: Matrix4[] = [];
    for (let i = 0; i < frameCount; i++) {
      const interpolatedPosition = startPosition
        .clone()
        .lerp(endPosition, i / frameCount);
      const interpolatedQuaternion = startQuaternion
        .clone()
        .slerp(endQuaternion, i / frameCount);
      const interpolatedScale = startScale
        .clone()
        .lerp(endScale, i / frameCount);

      const intermediateMatrix = new Matrix4().compose(
        interpolatedPosition,
        interpolatedQuaternion,
        interpolatedScale,
      );

      matrices.push(intermediateMatrix);
    }

    matrices.push(newMatrix);

    let step = 0;

    this.intervalId = setInterval(() => {
      if (step === matrices.length) {
        this.clearInterval();
      } else {
        if (this.pickedNode) {
          ctx2.cadScene.assisGroup.remove(this.pickedNode);
        }

        this.pickedNode = assisObj.cloneSelf();
        this.pickedNode.applyMatrix4(matrices[step]);

        ctx2.cadScene.assisGroup.add(this.pickedNode);
        ctx2.viewer.requestRender();

        step++;
      }
    }, this.duration / frameCount);
  }

  private clearInterval() {
    if (this.intervalId) {
      clearInterval(this.intervalId);
      this.intervalId = null;
    }
  }

  public clear() {
    this.clearInterval();

    if (this.pickedNode) {
      ctx2.cadScene.assisGroup.remove(this.pickedNode);
      ctx2.viewer.requestRender();

      this.pickedNode.destroy();
      this.pickedNode = null;
    }
  }
}

export const useAssemblyPreview = (
  fetchPreview: () => Promise<AssemblyPreview[] | undefined>,
  formData: any,
) => {
  useEffect(() => {
    const controller = new AbortController();
    const { signal } = controller;

    fetchPreview().then((previews?: AssemblyPreview[]) => {
      const animators = previews?.map(
        (assemblyPreview) => new AssemblyPreviewAnimator(assemblyPreview),
      );
      const clearAnimators = () =>
        animators?.forEach((animator) => animator.clear());

      if (signal.aborted) {
        clearAnimators();
      }

      signal.onabort = clearAnimators;
    });

    return () => {
      controller.abort();
    };
  }, [formData, fetchPreview]);
};
